﻿module UrlProcessor

open System
open System.IO
open System.IO.IsolatedStorage
open System.Reflection
open System.Threading
open System.Windows
open System.Windows.Controls
open System.Windows.Markup
open System.Windows.Media
open Details
open Keywords
open Links
open Violations

type KeyRec =
    { 
        Keyword : string
        Occurrence : int
        Density : float
    }

/// Displays a message box.
let showMsg msg = MessageBox.Show msg |> ignore

// Isolated storage file.
let store = IsolatedStorageFile.GetUserStoreForAssembly()

/// Generates and displays data for a URL.
let processUrl proj url context =
    async {
        let url' = hash url |> string
        let uri = new Uri(url, UriKind.Absolute)
        
        /// Loads a file saved in isolated storage.
        let load file =
            let path = Path.Combine(proj, url', file)
            use stream = new IsolatedStorageFileStream(path, FileMode.OpenOrCreate, store)
            use reader = new StreamReader(stream)
            reader.ReadToEnd()

        // Load the html.
        let html = load "html.txt"
        // Load the Web response headers.
        let headers = 
            let str = load "headers.txt"
            str.Split '\n' |> Array.map (fun x -> let x' = x.Split ','
                                                  x'.[0], x'.[1])
        // Load the request elapsed time.
        let time = load "time.txt"

        let contentLength = html.Length
        let lastMod = getHeader headers "LastModified"
        let server = getHeader headers "Server"
        let title = getElement patterns.[0] html
        let desc = getElement patterns.[1] html
        let keys = getElement patterns.[2] html
        let heading = getElement patterns.[3] html
        let keywords = indexHtml html
        let links = extractAllLinks uri html
        let violations = checkHtml html
        let arr = html.Split '\n'

        /// Sets the content of the details tab.
        let setDetailsTabContent (wnd : Window) =
            let urlTxtBox = wnd.FindName "url" :?> TextBox
            let contLenTxtBox = wnd.FindName "contLen" :?> TextBox
            let lastModTxtBox = wnd.FindName "lastMod" :?> TextBox
            let serverTxtBox = wnd.FindName "server" :?> TextBox
            let timeTxtBox = wnd.FindName "time" :?> TextBox
            let titleTxtBox = wnd.FindName "title" :?> TextBox
            let descTxtBox = wnd.FindName "desc" :?> TextBox
            let keywordsTxtBox = wnd.FindName "keywords" :?> TextBox
            let headingTxtBox = wnd.FindName "heading" :?> TextBox
            urlTxtBox.Text <- url
            contLenTxtBox.Text <- string contentLength
            lastModTxtBox.Text <- lastMod
            serverTxtBox.Text <- server
            timeTxtBox.Text <- string time
            titleTxtBox.Text <- title
            descTxtBox.Text <- desc
            keywordsTxtBox.Text <- keys
            headingTxtBox.Text <- heading

        /// Sets the content of the violations tab.
        let setViolationsTabContent (wnd : Window) =
            let violsList = wnd.FindName "violsList" :?> ListBox
            let createExpander (v : Violation) (lines : string []) =
                let header = v.Title
                let expander = Expander()
                let label = Label()
                let pos = match v.Index with
                            | Some index -> let rec getLineCol idx len =
                                                let l = lines.[idx].Length + 1
                                                let len' = l + len
                                                if len' > index then
                                                    idx + 1, l - (len' - index) + 1
                                                else
                                                    getLineCol (idx + 1) len'   
      
                                            let lineCol = getLineCol 0 0
                                            sprintf "\nLine: %d, column: %d" (fst lineCol) (snd lineCol)
                            | None -> ""

                label.Content <- v.Level.ToString() + "\n" + v.Description + "\n" + v.Recommendation + pos   
                expander.Header <- v.Title
                expander.Content <- label
                expander.Margin <- Thickness(5.)
                expander
            let arr = html.Split '\n'
            let violations' = violations |> Array.map (fun x -> createExpander x.Value arr)
            violsList.ItemsSource <- violations'


        /// Sets the content of the keywords tab.
        let setKeywordsTabContent (wnd : Window) =
            let dataGrid1 = wnd.FindName "datagrid1" :?> DataGrid
            let dataGrid2 = wnd.FindName "datagrid2" :?> DataGrid
            let dataGrid3 = wnd.FindName "datagrid3" :?> DataGrid
            let excelBtn = wnd.FindName "xprtexcel" :?> Button
            let list1 = keywords.[0]
            let list2 = keywords.[1]
            let list3 = keywords.[2]
            let export2Excel _ =
                try
                    async {
                        let app = Microsoft.Office.Interop.Excel.ApplicationClass(Visible = true)
                        let sheet = app.Workbooks.Add().Worksheets.[1] :?> Microsoft.Office.Interop.Excel._Worksheet
                        let setCellVal (col : string) row value =
                            sheet.Range(col + row).Value2 <- value
                        let send2Excel (lst : (string * int * float) list) col1 col2 col3 =
                            lst |> List.iteri (fun idx x -> let a, b, c = x
                                                            setCellVal col1 (string <| idx + 2) a
                                                            setCellVal col2 (string <| idx + 2) b
                                                            setCellVal col3 (string <| idx + 2) c) 
                        setCellVal "A" "1" "1 Word"
                        setCellVal "B" "1" "Occurrence"
                        setCellVal "C" "1" "Density (%)"
                        setCellVal "d" "1" "2 Words"
                        setCellVal "E" "1" "Occurrence"
                        setCellVal "F" "1" "Density (%)"
                        setCellVal "G" "1" "3 Words"
                        setCellVal "H" "1" "Occurrence"
                        setCellVal "I" "1" "Density (%)"

                        send2Excel list1 "A" "B" "C"
                        send2Excel list2 "D" "E" "F"
                        send2Excel list3 "G" "H" "I"
                    } |> Async.Start
                with _ -> showMsg "Failed to export data to Excel."

            dataGrid1.ItemsSource <- [| for x, y, z in list1 -> { Keyword = x; Occurrence = y; Density = z } |]
            dataGrid2.ItemsSource <- [| for x, y, z in list2 -> { Keyword = x; Occurrence = y; Density = z } |]
            dataGrid3.ItemsSource <- [| for x, y, z in list3 -> { Keyword = x; Occurrence = y; Density = z } |]
            excelBtn.Click |> Event.add export2Excel
    
        /// Sets the content of the links tab.
        let setLinksTabContent (wnd : Window) =
            let intLinksTxtBox = wnd.FindName "intLinks" :?> TextBox
            let extLinksTxtBox = wnd.FindName "extLinks" :?> TextBox
            let brokenLinksBtn = wnd.FindName "brokenlinks" :?> Button
//            let displayBrokenLinks (arr : BrokenLink []) =
//                let wnd = Window()
//                let dg = DataGrid()
//                wnd.Title <- "Broken Links"
//                wnd.Width <- 600.
//                wnd.Height <- 600.
//                dg.ItemsSource <- arr
//                wnd.Content <- dg
//                wnd.Show()
            let links1, links2 = links
            let links1' = links1 |> List.toArray
            let links2' = links2 |> List.toArray
            let str = String.Join("\n", links1')
            let str' = String.Join("\n", links2')
            let links' = fst links @ snd links
            let checkLinks _ =
                try
                    async { let context = SynchronizationContext.Current
                            checkBrokenLinks links' context } |> Async.Start
//                            do! Async.SwitchToContext context
//                            match brklinks.Length with
//                            | 0 -> showMsg "There are no broken links."
//                            | _ -> displayBrokenLinks brklinks } |> Async.Start
                with
                | _ -> showMsg "An error occured while checking broken links."
            intLinksTxtBox.Text <- str
            extLinksTxtBox.Text <- str'
            brokenLinksBtn.Click |> Event.add checkLinks

        /// Sets the content of the headers tab.
        let setHeadersTabContent (wnd : Window) =
            let headers' = headers |> Array.map (fun (x, y) -> sprintf "%s : %s" x y)
            let headTxtBlock = wnd.FindName "headers" :?> TextBlock
            headTxtBlock.Text <- String.Join("\n", headers')


        /// Sets the content of the HTML tab.
        let setHtmlTabContent (wnd : Window) =
            let htmlTxtBox = wnd.FindName "html" :?> TextBox
            let lineTxtBox = wnd.FindName "line" :?> TextBox
            let colTxtBox = wnd.FindName "col" :?> TextBox
            let findBtn = wnd.FindName "findbtn" :?> Button
            let lineNumbers = wnd.FindName "linenumber" :?> TextBox
            let validateBtn = wnd.FindName "validate" :?> Button
            let displayMarkupErrors (arr : MarkupViolation []) =
                let wnd = Window()
                let dg = DataGrid()
                wnd.Title <- "Markup Errors"
                wnd.Width <- 600.
                wnd.Height <- 600.
                wnd.WindowStartupLocation <- WindowStartupLocation.CenterScreen
                dg.ItemsSource <- arr
                wnd.Content <- dg
                wnd.Show()
            let numbers = arr |> Array.mapi (fun idx x -> string <| idx + 1)
            let findLineCol _ =
                let l = lineTxtBox.Text
                let c = colTxtBox.Text
                try 
                    let l' = int l
                    let c' = int c
                    let index = 
                        let len = 
                            [ 
                                for x in 0 .. (l' - 2) do
                                    yield arr.[x].Length + 1
                            ]
                            |> List.sum
                        len + c' - 1
                    htmlTxtBox.ScrollToLine(l' - 1)
                    htmlTxtBox.Select(index, arr.[l' - 1].Length - c') 
                    htmlTxtBox.SelectionOpacity <- 1.
                with _ -> showMsg "Invalid line and column numbers."
            let validateHtml _ =
                try 
                    let context = SynchronizationContext.Current
                    async { let! errs = validateMarkup uri
                            do! Async.SwitchToContext context
                            match errs.Length with
                            | 0 -> showMsg "The HTML is valid."
                            | _ -> displayMarkupErrors errs
                        } |> Async.Start
                with _ -> showMsg "HTML validation failed."
            htmlTxtBox.SelectionBrush <- Brushes.Yellow
            htmlTxtBox.Text <- html
            lineNumbers.Text <- (String.Join("\n", numbers))
            findBtn.Click |> Event.add findLineCol
            validateBtn.Click |> Event.add validateHtml 
                      
        // Load the xaml.
        let file = Assembly.GetExecutingAssembly().GetManifestResourceNames() |> Array.find (fun x -> x = "wind.xaml")
        use stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(file)
        do! Async.SwitchToContext context

        let wind = XamlReader.Load(stream) :?> Window

        setDetailsTabContent wind
        setViolationsTabContent wind
        setKeywordsTabContent wind
        setLinksTabContent wind
        setHeadersTabContent wind
        setHtmlTabContent wind
        wind.Show()
    }